package cc.verywell.pureblock.msg.nfs.hd;

import java.io.Closeable;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.FileTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Stream;

import cc.verywell.pureblock.func.node.KeyShell;
import cc.verywell.pureblock.log.Pblog;
import cc.verywell.pureblock.msg.nfs.UnsupportHashException;
import cc.verywell.pureblock.msg.nfs.idx.NfsIdxFileNotMatchException;
import cc.verywell.pureblock.msg.nfs.idx.NfsidxStructure;
import cc.verywell.pureblock.util.BasicEntry;
import cc.verywell.pureblock.util.StringUtil;
import cc.verywell.pureblock.util.sign.Sha;

/**
* 
* Copyright © 2019 TanYaqiu. All rights reserved.
* @author TanYaqiu 
* @address flat 601
* @date 2019年10月3日 下午8:56:11
*/
public class LfsHdMainV1 implements LfsHdSupply {

	public static int DIRS_MAX = 1<<14; //Linux ext3 最大新建文件夹数为 31998
	public static long FILE_CLUSTOR = 4*1024; //这个是磁盘一簇的空间， 一般系统是按此为单位占用空间的, 对于一些压缩文件夹，请调成1

	final protected Path filePath;
	final private long capacity;
	final private boolean usesize;
	
	private Object clearSpaceLock = new Object();
	private Object usedLock = new Object();
	private long used;
	private long spaceLocked;
	
	private ReentrantReadWriteLock initLock;
	private LfsHdNeed need;

	public LfsHdMainV1(Path filePath,LfsHdNeed need,long capacity) {
		this.filePath = filePath;
		this.capacity = capacity;
		this.need = need;
		this.usesize = capacity>0;
		this.initLock = new ReentrantReadWriteLock();
		
		this.initLock.writeLock().lock();
		initRun();/// 请在异步执行
	}
	
	public void initRun() {
		long time = Long.MAX_VALUE;
		try {
			time = System.currentTimeMillis(); //这个也是有可能出错的;
			if(Files.isDirectory(filePath)) {
				try(Stream<Path> sm = Files.list(filePath);){  //否则不会释放目录
					if(usesize) {
						sm.forEach(a->{//列出
							try {
								this.used += getFileSize(a);
							} catch (IOException e) {
								if(Pblog.FS)Pblog.FS(e);
							}
						});
					}
				}catch(IOException e) {
					if(Pblog.FS)Pblog.FS(e);
				}
			}
		}finally {
			ReentrantReadWriteLock initLock = this.initLock;
			if(initLock!=null) {
				try {
					initLock.writeLock().unlock();
				}finally {
					this.initLock = null;
				}
			}
			if(Pblog.DEBUG)Pblog.DEBUG(this," init time: ",System.currentTimeMillis()-time);
		}
	}

	@Override
	public byte[] read(NfsidxStructure idx) throws IOException {
		byte[] bs = new byte[(int)idx.getSize()];
		ByteBuffer bb = ByteBuffer.wrap(bs);
		read(idx, bb);
		if(bb.hasRemaining())throw new NfsIdxFileNotMatchException(idx+" remaining:"+bb.remaining());
		return bs;
	}
	@Override
	public int read(NfsidxStructure idx, ByteBuffer bb) throws IOException {
		return read(idx,bb,0,Integer.MAX_VALUE);
	}
	@Override
	public int read(NfsidxStructure idx, ByteBuffer bb, int offset, int length) throws IOException {
		Path p = filePath.resolve(idx.getPath());
		if(Pblog.DEBUG)Pblog.DEBUG("hdmap read ",p," offset:",idx.getOffset(),"+",offset," length:",idx.getSize(),":",length);
		try(
				RandomAccessFile fileaccess = new RandomAccessFile(p.toFile(), "r");
				FileChannel filechannel = fileaccess.getChannel();
				//FileLock filelock = filechannel.tryLock();
				) {
			;
			//long totalsize = filechannel.size();
			int readMax = (int)idx.getSize();
			if(length<readMax)readMax = length;
			int count;
			if(bb.remaining()>readMax) {
				int limitMark = bb.limit();
				bb.limit(bb.position()+readMax);
				count = filechannel.read(bb,idx.getOffset()+offset);
				bb.limit(limitMark);
				if(count!=readMax) {
					throw new NfsIdxFileNotMatchException(idx+" expect:"+readMax+" read:"+count);
				}
			}else {
				count = filechannel.read(bb,idx.getOffset()+offset);
				if(bb.hasRemaining())
					throw new NfsIdxFileNotMatchException(idx+" remaining:"+bb.hasRemaining());
			}
			return count;
		} catch (FileNotFoundException e) {
			throw new NfsIdxFileNotMatchException(idx+" file not exists!");
		} catch (IOException e) {
			if(Pblog.FS)Pblog.FS(e);
			throw e;
		}
	
		
//		try(AsynchronousFileChannel afc = AsynchronousFileChannel.open(p,StandardOpenOption.READ);){
//			if(Pblog.DEBUG)Pblog.DEBUG("hdmap read ",p," offset:",idx.getOffset(),"+",offset," length:",idx.getSize(),":",length);
//			//long size = Files.size(p);
//			int readMax = (int)idx.getSize();
//			if(length<readMax)readMax = length;
//			int count;
//			if(bb.remaining()>readMax) {
//				int limitMark = bb.limit();
//				bb.limit(bb.position()+readMax);
//				count = afc.read(bb,idx.getOffset()+offset).get();
//				bb.limit(limitMark);
//				if(count!=readMax) {
//					throw new NfsIdxFileNotMatchException(idx+" expect:"+readMax+" read:"+count);
//				}
//			}else {
//				count = afc.read(bb,idx.getOffset()+offset).get();
//			}
//		}catch(IOException e) {
//			if(Files.exists(p)) {
//				if(Pblog.FS)Pblog.FS(e);
//			}
//		} catch (InterruptedException e) {
//			if(Pblog.FS)Pblog.FS(e);
//		} catch (ExecutionException e) {
//			if(Pblog.FS)Pblog.FS(e);
//		}
	}

	//////////////
	
	/**
	 * 
	 */
	private Map<String,LockCanHold> lockmap = new ConcurrentHashMap<>();
	private Map<Object,HoldCollectLock> holdmap = new ConcurrentHashMap<>();

	@Override
	public void putTemp(KeyShell k, byte[] value) throws IOException{
		ReentrantReadWriteLock initLock = this.initLock;
		if(initLock!=null) {
			try {
				initLock.readLock().lock();
			}finally {
				initLock.readLock().unlock();
			}
		}
		String dir = encodeDir(k.key);
		LockCanHold lock = new LockCanHold(dir),oldlock;
		Path d = filePath.resolve(dir);
		//Path f = d.resolve(encodeFilename(key)); 
		while(lock!=null) {
			synchronized (lock) {
				//KeyShell k = new KeyShell(key);
				oldlock = lockmap.putIfAbsent(dir,lock);
				if( null==oldlock || oldlock==lock ) {
					///1.询问索引文件是否已经存在了,如果是就退出.
					NfsidxStructure idx = need.getIndex(k);
					if(idx!=null && !idx.isExternal())return;
					
					;
					try(
						SpaceLock spaceLock = new SpaceLock(k,value);
						){

						if(restSpace()<0) {
							clearSpace(spaceLock);//不够空间会直接抛出IOException, 给上一级知道这个已经挂了。
						}
						IOException e2 = null;
						for(int i = 0;i<10;i++) {
							Files.createDirectories(filePath);
							try(
									RandomAccessFile fileaccess = new RandomAccessFile(d.toFile(), "rw");
									FileChannel filechannel = fileaccess.getChannel();
									FileLock filelock = filechannel.lock(filechannel.size(),spaceLock.size,false);
								) {
									NfsidxStructure newids = write(dir, filechannel, spaceLock, k, value);
									need.fireHdTempStock(k, newids);
									return ; //添加成功
							}catch(IOException e1) {
								e2 = e1;
								if(e1.getMessage().indexOf("many open file")<0) { //什么鬼，谁能解析一下？ 如何在系统排队?
									break;
								}
							}
							Thread.sleep(i*100);
						}
						if(e2!=null) throw e2;
						throw new IOException("Unknow break out.");
//					} catch (IOException e) {
//						if(Pblog.FS)Pblog.FS(e);
					} catch (InterruptedException e) {
						if(Pblog.DEBUG)Pblog.DEBUG(e);
					}finally {
						LockCanHold freeLock = lock;
						lock = null; // 否则会进入死循环
						if(freeLock.isFree())
							lockmap.remove(dir);
					}
				}else {
					lock = oldlock;
				}
			}
		}
		throw new IllegalAccessError();
	}
	private NfsidxStructure write(String dir,FileChannel filechannel,SpaceLock spaceLock, KeyShell k, byte[] value) throws IOException {
		long oldSize = filechannel.size();
		ByteBuffer number1 = ByteBuffer.allocate(2);
		number1.putShort((short)k.key.length);
		number1.flip();
		ByteBuffer number2 = ByteBuffer.allocate(8);
		long position = oldSize+spaceLock.size-value.length;
		number2.putLong((position << NfsidxStructure.OFFSET_MOVE) | value.length);
		number2.flip();
		filechannel.position(oldSize);
		filechannel.write(new ByteBuffer[] {number1,ByteBuffer.wrap(k.key),number2,ByteBuffer.wrap(value)});
		
		spaceLock.setOldSize(oldSize);//确认写了后再用;
		return new NfsidxStructure(false,false,StringUtil.stringToByte(dir),position,value.length);
	}
	private void clearSpace(SpaceLock spaceLock) throws IOException,NoEnoughSpaceException {
		synchronized (clearSpaceLock) {
			if(usesize && getClustorSize(spaceLock.size)>capacity) {
				//当文件过大无法处理时，直接告知，免作无谓运算。
			}else if(restSpace()<0) {
				if(Files.isDirectory(filePath)) {
					try(Stream<Path> sm = Files.list(filePath);){ //列出
						ByteBuffer number1 = ByteBuffer.allocate(2);
						ByteBuffer number2 = ByteBuffer.allocate(8);
						sm.sorted((a,b)->{ //排序
							try {
								FileTime ta = Files.getLastModifiedTime(a);
								try {
									FileTime tb = Files.getLastModifiedTime(b);
									return ta.compareTo(tb);
								} catch (IOException e) {
									return 1;
								}
							} catch (IOException e) {
								if(Pblog.FS)Pblog.FS(e);
								return -1;
							}
							
						}).anyMatch(a->{
							String deldir = a.getFileName().toString().toLowerCase();
							LockCanHold dellock = new LockCanHold(deldir);//有可能会被其它lock获取
							synchronized (dellock) {
								LockCanHold olddellock = lockmap.putIfAbsent(deldir,dellock);
								if(olddellock==null) { // 正在使用(造成死锁)或已锁定都不会删除;
										//dellock 有可能会被其它lock获取,但在段try里面不会有数据，所以remove是必然的;
										ArrayList<BasicEntry<KeyShell,NfsidxStructure>> al = new ArrayList<>();
										try(
												RandomAccessFile fileaccess = new RandomAccessFile(a.toFile(), "rw");
												FileChannel filechannel = fileaccess.getChannel();
												FileLock filelock = filechannel.lock(0,filechannel.size(),true);
											) {
												for(;;) {
													///读取idx记录
													number1.clear();
													if(number1.capacity()!=filechannel.read(number1))break;
													number1.flip();
													byte[] keyhash = new byte[number1.getShort()];
													if(keyhash.length!=filechannel.read(ByteBuffer.wrap(keyhash)))break;
													number2.clear();
													if(number2.capacity()!=filechannel.read(number2))break;
													number2.flip();
													long length = number2.getLong();
													long position = length >>> NfsidxStructure.OFFSET_MOVE;
													length &= NfsidxStructure.SIZE_MARK;
													
													al.add(new BasicEntry<KeyShell,NfsidxStructure>(
															new KeyShell(keyhash),
															new NfsidxStructure(false,false,StringUtil.stringToByte(deldir),position,length)
													) );
							
													if(filechannel.position()==position)
														filechannel.position(position+length);
													else
														break; //文件损坏;
												}
										}catch(IOException e1) {
											if(Pblog.FS)Pblog.FS(e1);
										}finally {
											lockmap.remove(deldir);
										}
										if(!al.isEmpty()) {
											need.fireHdTempFreeSpace(al);
										}
										try {
											long delsize = getFileSize(a);
											if(Files.deleteIfExists(a)) {
												if(usesize) {
													synchronized (usedLock) {
														used -= delsize;
													}
												}
											}
										} catch (IOException e) {
											if(Pblog.FS)Pblog.FS(e);
										}
								}else {
									return false;
								}
							}
							try {
								return restSpace()>=0;
							} catch (IOException e) {
								if(Pblog.FS)Pblog.FS(e);
								return true; //终止遍历
							}
						});
						
					}catch(IOException e) {
						if(Pblog.FS)Pblog.FS(e);
					}
				}
				long r;
				if((r=restSpace())<0){
					long size = spaceLock.size;
					spaceLock.close(); //在同步内关闭，用以释放其它预留空间;
					throw new NoEnoughSpaceException("no enough space "+LfsHdSupply.formatSize(size)+", rest space "+LfsHdSupply.formatSize(r));
				}

			}
		}
	}
	
	
	
	
	@Override
	public NfsidxStructure putAndHoldInternal(Object hold, KeyShell k, byte[] value) throws IOException,NoEnoughSpaceException {
		ReentrantReadWriteLock initLock = this.initLock;
		if(initLock!=null) {
			try {
				initLock.readLock().lock();
			}finally {
				initLock.readLock().unlock();
			}
		}
		String dir = encodeDir(k.key);
		LockCanHold lock = new LockCanHold(dir),oldlock;
		Path d = filePath.resolve(dir);
		//Path f = d.resolve(encodeFilename(key)); 
		
		//先把holdLock准备好;
		HoldCollectLock holdLock = new HoldCollectLock(),oldholdLock;
		try {
			while(holdLock!=null) {
				synchronized (holdLock) {
					oldholdLock = holdmap.putIfAbsent(hold,holdLock);
					if( null==oldholdLock || oldholdLock==holdLock ) {
						holdLock.usingUp();
						break;
					}else {
						holdLock = oldholdLock; 
					}
					
				}
			}
			//此时holdLock已准备好;
			while(lock!=null) {
				synchronized (lock) {
					//KeyShell k = new KeyShell(key);
					oldlock = lockmap.putIfAbsent(dir,lock);
					if( null==oldlock || oldlock==lock ) {
						///1.询问索引文件是否已经存在了,如果是就核对，一致就退出.
						NfsidxStructure idxIfhave = need.getIndex(k);
						if(idxIfhave!=null && !idxIfhave.isExternal()) {
							try {
								byte[] oldValue = new byte[(int)idxIfhave.getSize()];
								ByteBuffer bb = ByteBuffer.wrap(oldValue);
								if(idxIfhave.getSize() == read(idxIfhave, bb)) {
									if(Arrays.equals(value, oldValue)) {
										if(!dir.equals(idxIfhave.getPath()))throw new UnsupportHashException("key store file ["+dir+"] not match point Idx ["+idxIfhave+"]");
										//将hold 与 lockID绑定
										holdLock.put(lock);
										return idxIfhave; //完全一致，返回idx即可，无需新建立;
									}else{
										//正的找到了？ 再次复核;
										byte[] thekey = Sha.getInstance().hash(oldValue);
										if(!Arrays.equals(k.key, thekey))throw new UnsupportHashException("point Idx ["+idxIfhave+"] not the same key with given.");
										thekey = Sha.getInstance().hash(value);
										if(!Arrays.equals(k.key, thekey))throw new UnsupportHashException("point Idx not the same key with given.");
										Pblog.SECURE("find 1 key 2 value key: ",k.key);
										Pblog.SECURE("find 1 key 2 value value1: ",value);
										Pblog.SECURE("find 1 key 2 value value2: ",oldValue);
										throw new UnsupportHashException("!!! find 1 key 2 value!");
									}
								}
							}catch(UnsupportHashException e) {
								throw new IOException(e);
							}catch(IOException e) { // read出现的任何事故都会在这里消化掉;
								if(Pblog.FS)Pblog.FS(e);
							}
						}
						
						try(
							SpaceLock spaceLock = new SpaceLock(k,value);
							){

							if(restSpace()<0) {
								clearSpace(spaceLock);//不够空间会直接抛出IOException, 给上一级知道这个已经挂了。
							}
							IOException e2 = null;
							for(int i = 0;i<10;i++) {
								Files.createDirectories(filePath);
								try(
										RandomAccessFile fileaccess = new RandomAccessFile(d.toFile(), "rw");
										FileChannel filechannel = fileaccess.getChannel();
										FileLock filelock = filechannel.lock(filechannel.size(),spaceLock.size,false);
									) {
										NfsidxStructure newids = write(dir, filechannel, spaceLock, k, value);
										need.fireHdTempStock(k, newids);
										//将hold 与 lockID绑定
										holdLock.put(lock);
										return newids; //添加成功
								}catch(IOException e1) {
									e2 = e1;
									if(e1.getMessage().indexOf("many open file")<0) { //什么鬼，谁能解析一下？ 如何在系统排队?
										break;
									}
								}
								Thread.sleep(i*100);
							}
							if(e2!=null) throw e2;
							throw new IOException("Unknow break out.");
//						} catch (IOException e) {
//							if(Pblog.FS)Pblog.FS(e);
						} catch (InterruptedException e) {
							if(Pblog.DEBUG)Pblog.DEBUG(e);
						}finally {
							LockCanHold freeLock = lock;
							lock = null; // 否则会进入死循环
							if(freeLock.isFree())
								lockmap.remove(dir);
						}
					}else {
						lock = oldlock;
					}
				}
			}
			throw new IllegalAccessError();
			
		}finally {
			synchronized (holdLock) {
				holdLock.usingDown();
				if(holdLock.isFree()) {
					holdmap.remove(hold, holdLock);
				}
			}
		}
		
	}
	
	
	


	@Override
	public void removeHold(Object hold) {
		HoldCollectLock holdLock = holdmap.get(hold),oldholdLock;
		try {
			HashSet<LockCanHold> holdlist = null;
			
			while(holdLock!=null) {
				synchronized (holdLock) {
					oldholdLock = holdmap.putIfAbsent(hold,holdLock);
					if( null==oldholdLock || oldholdLock==holdLock ) {
						holdLock.usingUp();
						holdlist = holdLock.finish();
						break;
					}else {
						holdLock = oldholdLock; 
					}
					
				}
			}
			
			for (LockCanHold lockCanHold : holdlist) {
				synchronized (lockCanHold) {
					lockCanHold.remove(this);
					if(lockCanHold.isFree())
						lockmap.remove(lockCanHold.dir,lockCanHold);
				}
			}
			
		}finally {
			synchronized (holdLock) {
				holdLock.usingDown();
				if(holdLock.isFree()) {
					holdmap.remove(hold, holdLock);
				}
			}
		}
	}

	/**
	 * 所有操作都应该在sync之内完成;
	 */
	final private static class LockCanHold{
		final public String dir;
		public LockCanHold(String dir) {
			this.dir = dir;
		}
		private HashSet<Object> holdlist = null;
		/**
		 * 仅由HoldCollectLock.put调用
		 * @param hold
		 */
		void put(Object hold) {
			if(holdlist==null)holdlist = new HashSet<Object>(1);
			holdlist.add(hold);
		}
		/**
		 * 仅由HoldCollectLock.remove调用
		 * @param hold
		 */
		void remove(Object hold) {
			if(holdlist!=null) {
				holdlist.remove(hold);
				if(holdlist.isEmpty())holdlist = null;
			}
		}
		public boolean isFree() {
			return holdlist == null;
		}
	}
	private static class HoldCollectLock{
		private int threadUsing = 0;
		private boolean finished = false;
		private HashSet<LockCanHold> holdlist = null;
		public synchronized void put(LockCanHold lock) { //此时必然在lock的sync内
			if(finished) {
				if(Pblog.FS)Pblog.FS("puting file to a finished Hold");
				return; //这个类已经取消使用了,默认忽略此次操作
			}
			if(holdlist==null)holdlist = new HashSet<LockCanHold>(1);
			if(holdlist.add(lock)) {
				lock.put(this);
			}
		}
		public boolean isFree() {
			return holdlist == null && threadUsing==0;
		}
		public void usingUp() {this.threadUsing++;}
		public void usingDown() {this.threadUsing--;}

		
		public HashSet<LockCanHold> finish() {
			finished = true;
			HashSet<LockCanHold> h = holdlist;
			holdlist = null;
			return h;
		}
	}

	////////////////////////////////////////////////////////////////////
	protected String encodeDir(byte[] key) {
		int lockID = Arrays.hashCode(key) % DIRS_MAX;
		if(lockID<0)lockID=~lockID;
		return Integer.toHexString(lockID)+".d".toLowerCase();
	}
	protected long restSpace() throws IOException {
		long restspace = 0;
		Path p = filePath;
		while(p!=null && !Files.exists(p)) {
			p = p.getParent();
		}
		long freespace = Files.getFileStore(p).getUsableSpace();
		synchronized(usedLock) {
			if(usesize) {
				restspace = capacity - used;
				if(restspace>freespace)
					restspace=freespace;
			}else{
				restspace = freespace+capacity;
			}
			restspace -= spaceLocked;
		}
		return restspace;
	}
	class SpaceLock implements Closeable{
		long size;
		long oldsize;
		public SpaceLock(KeyShell k, byte[] value) {
			this(2+k.key.length+8+value.length);
		}
		public SpaceLock(long size) {
			this.size = size;
			long lock = getClustorSize(size);
			synchronized(usedLock) {
				spaceLocked += lock;
			}
		}
		public void setOldSize(long oldsize) {
			this.oldsize = oldsize;
		}
		@Override
		public void close() {
			if(size==0)return;
			long lock = getClustorSize(size);
			synchronized(usedLock) {
				used += getClustorSize(oldsize+size)-getClustorSize(oldsize);
				spaceLocked -= lock;
			}
			size=oldsize=0;
		}
	}
	
	protected static long getFileSize(Path p) throws IOException{
		long size = Files.size(p);
		if(size==0)return size;
		return getClustorSize(size);
	}
	final protected static long getClustorSize(final long size) {
		if(size<=0)return 0;
		return ((size-1)/FILE_CLUSTOR+1)*FILE_CLUSTOR;
	}

	
	
	
	
}
